package mond_xml;

use Data::Dumper;

#########################################################################
# Perl Object Module to handle loading/parsing of XML event monitor	#
# configuration files - which are in two basic formats - application 	#
# and node.								#
#									#
# NODE CONFIGURATION EXAMPLE						#
#									#
# <?xml version="1.0" standalone="yes"?>
# <node_monitoring_configuration>
#	<monitor name="swap" type="swap_space" interval="5 mins">
#		<instance days="all" from="08:00" to="17:00" rearm="5 mins" quiesce="10 mins">
#			<action threshold="95%" todo="send_alert,move_apps"/>
#		</instance>
#		<instance rearm="30 mins">
#			<action threshold="80%" failcount="0,3" todo="send_alert,move_one_app"/>
#			<action threshold="80%" failcount="4," todo="move_apps"/>
#		</instance>
#	</monitor>
# </node_monitoring_configuration>
#
# APPLICATION CONFIGURATION EXAMPLE
#
# <?xml version="1.0" standalone="yes"?>
# <application_monitoring_configuration>
#	<monitor name="pidmonitor" type="process" interval="10 secs" value="procstring">
#		<instance days="all" from="08:00" to="17:00" rearm="30 mins">
#			<action occurrence_minmax="1,3" todo="script:/app/restart"/>
#			<action occurrence_minmax="4,9999" todo="send_alert,move_app"/>
#		</instance>
#		<instance>
#			<action occurrence_minmax="1,3" failcount="0,3" todo="script:/app/restart"/>
#			<action occurrence_minmax="1,3" failcount="4," todo="move_app"/>
#			<action occurrence_minmax="4,9999" todo="send_alert,move_app"/>
#		</instance>
#	</monitor>
# </application_monitoring_configuration>
#
# The contents are actually written to a hash of hashes where each name is in the
# following format:
#
# e.name.N - 	where N starts at 0 and increases for each element of the same
# 		name. 
# a.name -	An attribute - which must be in an element. Please note that e.
#		entries can of course be nested.
# DATA	- 	Data associated with element.
#
# The failcount defaults to 0,99999 if not specified. The failcount is
# reset when the rearm period is over.
#
#########################################################################

use XML::Parser;
use Carp qw(cluck);
use strict;

my $error_str=undef;
my $logmsg_hdlr=undef;
my $warnmsg_hdlr=undef;

sub error {
	return $error_str;
}

sub set_logmsg_hdlr {
	$logmsg_hdlr=$_[0];
}

sub set_warnmsg_hdlr {
	$warnmsg_hdlr=$_[0];
}

sub msg {
	if($_[0] eq "log" && defined($logmsg_hdlr)) {
		&{$logmsg_hdlr}($_[1]);
	}
	if($_[0] eq "warn" && defined($warnmsg_hdlr)) {
		&{$logmsg_hdlr}($_[1]);
	}
}

#########################################################################
# Class methods:							#
# ==============							#
# error - return latest error string or undef if everything was OK.	#
# set_logmsg_hdlr -  the routine to call to log a message - no logging	#
# 		     occurs if not set.					#
# set_warnmsg_hdlr - Issue a warning message via this routine - if	#
#		     available.						#
#########################################################################

#########################################################################
# 									#
# The new method does not expect any arguments since the contents to	#
# parse are expected to be passed as a string using the parse method.	#
#									#
#########################################################################

sub new {
my $self;
	#################################################################
	# Now load in the XML using the XML::Parser - not XML simple.	#
	#################################################################
	
	$self={};
	$self->{t}=undef;
	$self->{type}=-1;
	$self->{loaded}={};
	$self->{valid_structure}=0;
	$self->{valid_content}=0;
	$self->{valid_actions}=[];
	$self->{valid_node_monitors}=[];
	$self->{valid_app_monitors}=[];
	bless($self);
	return $self;
}

#########################################################################
# The below is used to set the names of valid node and application	#
# monitors. This routine does not validate them - the caller should	#
# have done that.							#
#########################################################################

sub set_valid_monitors {
my $self=shift;
my $type=shift;
my @list=();

	for my $a (@_) {
		push @list,$a;
	}
	if($type ne "node" && $type ne "application") {
		return(0,"Unknown monitor type specified.");
	}
	if($type eq "node") {
		$self->{valid_node_monitors}=[@list];
	} else {
		$self->{valid_app_monitors}=[@list];
	}
	return(1);
}

#########################################################################
# The caller must use this to define a list of valid actions that are	#
# available to the monitor when a certain action is to be performed.	#
#########################################################################

sub set_valid_actions {
my $self=shift;
my @list=();

	for my $a (@_) {
		push @list,$a;
	}
	$self->{valid_actions}=[@list];
}

sub parse {
my $self=shift;
my $data=shift;
local $mond_xml::tree=undef;

	local %mond_xml::_h=();
	local $mond_xml::chash_ref;
	$error_str=undef;
	my $p;
	$p = new XML::Parser(
			Handlers => { Start => \&lc_handle_start,
					End => \&lc_handle_end,
				       Char => \&lc_handle_char },
			ErrorContext => 5
		);
	$error_str=undef;
	$p->parse($data);
	if(defined($error_str)) {
		cluck($error_str) if $::DEBUG;
		return 0;
	}
	$self->{t}={%mond_xml::_h};
	return 1;
}

sub lc_handle_start {
my $parser=shift;
my $element=shift;
my %attrs=(@_);
my @context=$parser->context;
my @required=qw(name hosts text routine);
my @optional=qw(mandatory optional);

	$element="\L$element";
	$mond_xml::__chardata="";

	#################################################################
	# Walk the context to get the latest tree component to 		#
	# update.							#
	#################################################################

	my $ch=\%mond_xml::_h;
	for my $c (@context) {
		my $l=0;
		while(exists($ch->{"e.$c.$l"})) {
			$l++;
		}
		$l--;
		$ch=$ch->{"e.$c.$l"};
	}

	#################################################################
	# So we look for all e.$element.n in $ch and add one ...	#
	#################################################################

	my $l=0;
	while(exists($ch->{"e.$element.$l"})) {
		$l++;
	}
	$ch->{"e.$element.$l"}={};
	$mond_xml::chash_ref=$ch->{"e.$element.$l"};
	$ch=$ch->{"e.$element.$l"};

	#################################################################
	# We can add all the attributes now...				#
	#################################################################

	for my $ckey (keys %attrs) {
		$ch->{"a.$ckey"}=$attrs{$ckey};
	}
}

sub lc_handle_end {
my $parser=shift;
my $element=shift;

	$mond_xml::__chardata =~ s/^\s+//g;	# Get rid of leading white
	$mond_xml::__chardata =~ s/\s+$//g;	# Get rid of trailing white space
	$mond_xml::__chardata =~ s/\n+$//g;	# Get rid of newlines white space
	if(length($mond_xml::__chardata)) {
		$mond_xml::chash_ref->{DATA}=$mond_xml::__chardata;
	}
}

sub lc_handle_char {
my $parser=shift;
my $str=shift;

	$mond_xml::__chardata.=$str;
	return ;
}

#########################################################################
# Once the tree has been defined a series of routines can be used to	#
# quickly gain access to the contents...				#
# get_element(name) - returns three items - data, attributes, elements.	#
#									#
# For example get_element("node_monitoring_configuration#0") would	#
# return "",{},["node_monitoring_configuration#0|monitor#0"]		#
# and then it would be possible to call:				#
# get_element("node_monitoring_configuration#0|monitor#0") and this	#
# would return three values:						#
# "",{name=>"swap",type=>"swap_space",interval=>"5 mins"},		#
# ["node_monitoring_configuration#0|monitor#0|instance#0",		#
#  "node_monitoring_configuration#0|monitor#0|instance#1"]		#
#									#
# So makes it very easy to walk the tree to get the stuff you want.	#
#########################################################################

sub get_element($$) {
my $self=shift;
my $xpath=shift;

	my $r=$self->{t};
	return undef if !defined($r);
	my @F=split(/\|/,$xpath);
	for my $cpart (@F) {
		my ($el,$num)=split(/#/,$cpart);
		$num="0" if ! defined($num);

		#########################################################
		# e.$el.$num should be a key in the current value of r,	#
		# and return undef if not.				#
		#########################################################
		if(!exists($r->{"e.$el.$num"})) {
			return undef;
		}
		$r=$r->{"e.$el.$num"};
	}

	#################################################################
	# Now $r is the one where we expect data, attributes and 	#
	# elements that should be returned to the user.			#
	#################################################################

	my $data=undef;
	if(exists($r->{DATA})) { 
		$data=$r->{DATA};
	} else {
		$data="";
	}
	my @elements=();
	my %attrs=();

	for (sort(keys %$r)) {
		next if $_ eq "DATA";
		if(/^e\./) {
			my $x=substr($_,2); $x =~ s/\./#/;
			push @elements,"$xpath|$x";
		} else {
			my $x=substr($_,2); $x =~ s/\.\d+$//;
			$attrs{$x}=$r->{$_};
		}
	}
	return ($data,{%attrs},[@elements]);
}

#################################################################
# Days can be "all" or "mon","tue","wed", ... - and can be	#
# a comma separated list as well. We ignore all chars after 	#
# the first three.						#
#################################################################

sub _valid_days($) {
my $l=shift;
my @days=qw(all mon tue wed thu fri sat sun);

	my @F=split(/,/,lc($l));
	@F=map {substr($_,0,3);} @F;
	for my $xx (@F) {
		if(! grep {$xx eq $_} @days) {
			return(0);
		}
	}
	return(1);
}

#########################################################################
# Valid thresholds are always in %.					#
#########################################################################

sub _valid_threshold($) {
my $x=shift;

	if($x !~ /^\d+\%$/) {
		return(0);
	}
	return(1);
}

sub _valid_time($) {
my $x=shift;
	return(1) if $x =~ /^\d{2}:\d{2}$/;
	return(0);
}

sub _valid_interval($) {
my $f=shift;

	$f=~s/^\s+//; $f=~s/\s+$//;
	my @F=split(/\s+/,$f);
	if(@F!=2) {
		return(0);
	}
	if($F[0] !~ /^\d+$/) {
		return(0);
	}
	$F[1]=lc($F[1]);
	if(! grep {$_ eq $F[1]} qw(s sec secs second seconds m min mins minute minutes h hour hours)) {
		return(0);
	}
	return(1);
}

sub _convert_interval($) {
my $f=shift;

	$f=~s/^\s+//; $f=~s/\s+$//;
	my @F=split(/\s+/,$f);
	if(@F!=2) {
		push @F,"secs";
	}
	if($F[0] !~ /^\d+$/) {
		return(-1);
	}
	$F[1]=lc($F[1]);
	my $mult;
	if(grep {$_ eq $F[1]} qw(s sec secs second seconds)) {
		$mult=1;
	}
	if(grep {$_ eq $F[1]} qw(m min mins minute minutes)) {
		$mult=60;
	}
	if(grep {$_ eq $F[1]} qw(h hour hours)) {
		$mult=3600;
	}
	return int($F[0])*$mult;
}

#########################################################################
# This can be in the form [N,N] or [,N] or [N,] but not , only!	N can	#
# be "[\-\+]{1}\d+".							#
#########################################################################

sub _valid_occurrence_minmax($) {
my $x=shift;

	if($x =~ /^\d+$/) {
		$x.=",$x";
	}
	my @F=split(/,/,$x);
	if(@F!=2) {
		return(0);
	}
	if(!defined($F[0]) && !defined($F[1])) {
		return(0);
	}
	if(!defined($F[0])) { $F[0]="0"; }
	if(!defined($F[1])) { $F[1]="99999"; }
	if($F[0] !~ /^\d+$/ || $F[1] !~ /^\d+$/) {
		return(0);
	}
	return(1);
}

#########################################################################
# For a bit of flexibility there are a couple of formats that are	#
# allowed here:								#
# script:/path - run the specified script - arguments can be		#
# 		 present.						#
# name	       - Run specified check with no additional args.		#
# name(X=Y|..) - Run the specified check with named argunent X as	#
#		 value Y. These args will be passed to the action.	#
#		 Currently move_app supports APPNAME to specify the	#
#		 application if not running for an application monitor.	#
#									#
# Multiple actions can be specified - they can be comma separated.	#
#########################################################################

sub _valid_actions($$) {
my $self=shift;
my $x=shift;

	my @F=split(/,/,$x);
	my @B=();
	for my $act (@F) {
		if($act =~ /^script:\/[\s_a-zA-Z0-9]+/) {
			next;
		}
		if($act =~ /\(/) {
			my ($act2,$aa)=($act =~ /([a-zA-Z_]+)\((.*)\)/);
			if(!defined($act2) || !defined($aa)) {
				push @B,$act;
				next;
			}
			if(! grep {$act2 eq $_} @{$self->{valid_actions}}) {
				push @B,$act;
			}
			#################################################
			# Action is valid, so check args		#
			#################################################
			my @F2=split(/\|/,$aa);
			for my $carg (@F2) {
				my ($aaa,$bbb)=split(/=/,$carg);
				if(!defined($aaa) || !defined($bbb)) {
					push @B,$act;
				}
			}
			next;
		}
		if(! grep {$act eq $_} @{$self->{valid_actions}}) {
			push @B,$act;
		}
	}
	if(@B) {
		return join(",",@B);
	}
	return undef;
}


# We take the generate xml tree and now ensure we have the correct format ...

sub _parse_structure {
my $self=shift;
my $t=$self->{t};
my $t2=$t;

	my @K=keys(%$t);
	$self->{valid_structure}=1;
	if(@K!=1) {
		return(0,"Only a single top level entity must be present.");
	}
	if($K[0] ne "e.application_monitoring_configuration.0" && 
	   $K[0] ne "e.node_monitoring_configuration.0") {
	   	return(0,"Unrecognised top level element - should be 'application_monitoring_configuration' or 'node_monitoring_configuration'.");
	}

	$t2=$t->{$K[0]};

	#################################################################
	# At this level all elements should be e.monitor.N and if not	#
	# a warning should be issued.					#
	#################################################################
	my @mons=();
	my @bad=();
	for my $ckey (keys %$t2) {
		my $x=$ckey; $x =~ s/^[ae]{1}\.//; $x =~ s/\.\d+$//;
		if($ckey !~ /^e\.monitor\.\d+$/) {
			push @bad,$x;
		} else {
			push @mons,[$ckey,$x];
		}
	}
	if(@bad) {
		return(0,"Unexpected 2nd level entries found - all should be 'monitor' but found: " . join(",",@bad) . ".");
	}

	#################################################################
	# We have a list of monitors, so we check each one in turn.	#
	#################################################################

	for my $cmonitor (@mons) {
		my $key=$cmonitor->[0];
		my $rkey=$cmonitor->[1];
		my $ref=$t2->{$key};
		my @c_attrs=(keys %$ref);
		#########################################################
		# Check to ensure that the manditory attributes have	#
		# been specified.					#
		#########################################################
		my @missing=();
		for my $cattr (qw(name type interval)) {
			if(! grep {$_ eq "a.$cattr"} @c_attrs) {
				push @missing,$cattr;
			}
		}
		my $monitor_name=$ref->{"a.name"};
		if(@missing) {
			return(0,"Missing mandatory attributes: " . join(",",@missing) . ".");
		}
		@bad=();
		for my $cattr (@c_attrs) {
			if(substr($cattr,0,2) eq "a.") {
				if(! grep {$_ eq $cattr} ("a.value","a.name","a.type","a.interval")) {
					my $x=$cattr; $x =~ s/^[ae]{1}\.//; $x =~ s/\.\d+$//;
					push @bad,$x;
				}
			}
		}
		if(@bad) {
			return(0,"Invalid attributes found in 'monitor' entry '$monitor_name': " . join(",",@bad) . ".");
		}
		#########################################################
		# Now check that the only elements found in this 	#
		# monitor are instance elements.			#
		#########################################################
		my @instances=();
		for my $cattr (@c_attrs) {
			if($cattr !~ /^e\./) {
				next;
			}
			if($cattr !~ /^e\.instance\.\d+$/) {
				my $x=$cattr; $x =~ s/^[ae]{1}\.//; $x =~ s/\.\d+$//;
				push @bad,$x;
			} else {
				push @instances,$cattr;
			}
		}
		if(@bad) {
			return(0,"Invalid elements found in 'monitor' name '$monitor_name': " . join(",",@bad) . ".");
		}
		@instances=sort(@instances);
		#########################################################
		# Now @instances is a list of instance keys in $ref - 	#
		# so we now should be able to check all attributes	#
		# are known - and that the mandatory ones are 		#
		# present.						#
		#########################################################
		my @inst_attrs_allowed=qw(days from to rearm quiesce);
		my @inst_attrs_required=qw(rearm);
		my $inst_num=0;
		for my $cinst (@instances) {
			$inst_num++;
			my @had=(); my @bad=(); @missing=();
			my $r=$ref->{$cinst};
			for my $ckey (keys %$r) {
				next if $ckey !~ /^a\./;
				my $k=$ckey; $k = substr($ckey,2);
				if(! grep {$_ eq $k} @inst_attrs_allowed) {
					push @bad,$k; next;
				}
				push @had,$k;
			}
			if(@bad) {
				return(0,"Invalid attributes found in instance $inst_num of monitor '$monitor_name': " . join(",",@bad) . " ");
			}
			#########################################################
			# Check to ensure all mandatory ones are present.	#
			#########################################################
			for my $chad (@inst_attrs_required) {
				if(!grep {$_ eq $chad} @had) {
					push @missing,$chad;
				}
			}
			if(@missing) {
				return(0,"Required attributes missing in instance $inst_num of monitor '$monitor_name': " . join(",",@missing) . " ");
			}

			#########################################################
			# We also need to check that the only elements found	#
			# are action elements - and at least one is present.	#
			#########################################################
			my @actions=();
			@bad=();
			for my $ckey (keys %$r) {
				next if $ckey !~ /^e\./;
				my $x=$ckey; $x=~s/^e\.//; $x=~s/\.\d+$//;
				if($x ne "action") {
					push @bad,$x;
				} else {
					push @actions,$ckey;
				}
			}
			if(@bad) {
				return(0,"Invalid elements found in instance $inst_num of monitor '$monitor_name': " . join(",",@bad) . " ");
			}
			@actions=sort(@actions);
			if(! @actions) {
				return(0,"No action elements found for instance $inst_num of monitor '$monitor_name'.");
			}
			#########################################################
			# Finally for each of the actions we need to check	#
			# to ensure the correct attributes are set...		#
			#########################################################
			my $action_inst=0;
			@bad=();
			my @badels=();
			my @action_attrs_allowed=qw(occurrence_minmax todo threshold failcount);
			for my $caction (@actions) {
				::msg("Action=$caction");
				$action_inst++;
				for my $ckey (keys %{$r->{$caction}}) {
					next if $ckey eq "DATA";
					if($ckey =~ /^e\./) {
						my $x=$ckey; $x=~s/^e\.//; $x=~s/\.\d+$//;
						push @badels,$x; next;
					}
					my $z=$ckey; $z=~s/^a\.//; $z=~s/\.\d+$//;
					if(! grep {$z eq $_} @action_attrs_allowed) {
						push @bad,$z;
					}
				}
				if(@badels) {
					return(0,"Invalid elements found in action instance $action_inst of instance $inst_num of monitor '$monitor_name': " . join(",",@badels) . ".");
				}
				if(@bad) {
					return(0,"Invalid attributes found in action instance $action_inst of instance $inst_num of monitor '$monitor_name': " . join(",",@bad) . ".");
				}
			}
		}
	}
	$self->{valid_structure}=1;
	return(1,undef);
}

sub validate_structure {
my $self=shift;

	return(_parse_structure($self));
}

sub validate_entries {
my $self=shift;

	if(!defined($self->{t})) {
		return(0,"No configuration file parsed as yet!");
	}
	if(!$self->{valid_structure}) {
		return(0,"Structure of configuration has not yet been validated.");
	}

	$self->{valid_content}=0;
	# Ok let's go and find out if the top level node is one of the	#
	# ones we expect.

	my $type=-1; # 0 - node type, 1 = app type

	my ($DATA,$attrs,$elements)=get_element($self,"node_monitoring_configuration#0");
	if(!defined($attrs)) {
		($DATA,$attrs,$elements)=get_element($self,"application_monitoring_configuration#0");
		$type=1;
	} else {
		$type=0;
	}
	$self->{type}=$type;
	if(!defined($attrs)) {
		return(0,"Top level element is not 'node_monitoring_configuration' or 'application_monitoring_configuration'.");
	}

	if($type==1) {
		my ($DATA2,$attrs2,$elements2)=get_element($self,"application_monitoring_configuration#0|monitor#0");
		if(exists($attrs2->{value}) && defined($attrs2->{value})) {
			$self->{value}=$attrs2->{value};
		} else {
			$self->{value}="";
		}
	} else {
		my ($DATA2,$attrs2,$elements2)=get_element($self,"node_monitoring_configuration#0|monitor#0");
		if(exists($attrs2->{value}) && defined($attrs2->{value})) {
			$self->{value}=$attrs2->{value};
		} else {
			$self->{value}="";
		}
	}

	#########################################################################
	# The elements should be all monitor# types - since we've validated	#
	# the structure to get this far ... hence we scan each one in turn.	#
	#########################################################################
	
	for my $cpath (@$elements) {
		my ($DATA2,$attrs2,$elements2)=get_element($self,$cpath);
		return(0,"Unexpected error #1") if !defined($attrs2);

		#################################################################
		# For each monitor we now scan the attributes to ensure they	#
		# are valid.							#
		#################################################################
		my @e=();
		for my $cattr (keys %$attrs2) {
			if($cattr eq "name") {
				my $x=$attrs2->{$cattr};
				if($x !~ /^[a-zA-Z0-9_\-]{1,}$/) {
					push @e,"The 'name' attribute for monitor '$cpath' is not in the expected format.";
				}
				next;
			}
			if($cattr eq "interval") {
				my $r=_valid_interval($attrs2->{$cattr});
				if(!$r) {
					push @e,"The 'interval' attribute value of '$attrs2->{$cattr}' for monitor '$cpath' is not in the expected format.";
				}
				next;
			}
			#################################################################
			# A valid value for type will depend on whether type is 0	#
			# [for a node monitor] or 1 [an application monitor].		#
			#################################################################
			if($cattr eq "type") {
				my $x=$attrs2->{$cattr};
				if($type==0) {
					if(! grep {$x eq $_} @{$self->{valid_node_monitors}}) {
						push @e,"Node monitor type '$x' for monitor '$cpath' is not valid.";
						next;
					}
				} else {
					if(! grep {$x eq $_} @{$self->{valid_app_monitors}}) {
						push @e,"Application monitor type '$x' for monitor '$cpath' is not valid.";
						next;
					}
				}
				next;
			}
			#################################################################
			# For application/node monitors we also need a value, so	#
			# capture that one if found.					#
			#################################################################
			if($cattr eq "value") {
				next;
			}
			push @e,"Unexpected attribute '$cattr' found in monitor '$cpath'.";
		}
		#########################################################
		# We've compared all the attributes for the current 	#
		# monitor, so now check all elements it contains are	#
		# instance#nnn type and if not complain.		#
		#########################################################
		for my $celement2 (@$elements2) {
			my @F=split(/\|/,$celement2);
			if($F[$#F] !~ /^instance#\d+$/) {
				push @e,"Unrecognised element found: $celement2.";
				next;
			}
			#########################################################
			# Now get the details at this level and check the	#
			# attributes are first as a first thing to do...	#
			#########################################################
			my ($DATA3,$attrs3,$elements3)=get_element($self,$celement2);

			for my $cattr (keys %$attrs3) {
				my $x=$attrs3->{$cattr};
				if($cattr eq "rearm") {
					if(!_valid_interval($x)) {
						push @e,"Attribute given for 'rearm' ['$x'] is not valid for action '$celement2'.";
					}
					next;
				}
				if($cattr eq "quiesce") {
					if(!_valid_interval($x)) {
						push @e,"Attribute given for 'quiesce' ['$x'] is not valid for action '$celement2'.";
					}
					next;
				}
				if($cattr eq "days") {
					if(!_valid_days($x)) {
						push @e,"Attribute given for 'days' ['$x'] is not valid for action '$celement2'.";
					}
					next;
				}
				if($cattr eq "from" || $cattr eq "to") {
					if(!_valid_time($x)) {
						push @e,"Attribute given for '$cattr' ['$x'] is not valid for action '$celement2'.";
					}
					next;
				}
				push @e,"Unexpected attribute '$cattr' found in action '$celement2'.";
			}
			#########################################################
			# Now check that all elements are action type, and	#
			# give an error if not.					#
			#########################################################

			for my $celement3 (@$elements3) {
				my @F=split(/\|/,$celement3);
				if($F[$#F] !~ /^action#\d+$/) {
					push @e,"Unexpected element found: $celement3.";
					next;
				}
				#########################################################
				# Now get the attributes/elements of this element.	#
				#########################################################
				my ($DATA4,$attrs4,$elements4)=get_element($self,$celement3);
				for (@$elements4) {
					push @e,"Unexpected element found: $_";
				}
				for my $cattr (keys %$attrs4) {
					my $x=$attrs4->{$cattr};
					if($cattr eq "todo") {
						my $r=_valid_actions($self,$x);
						if(defined($r)) {
							push @e,"Invalid actions found for 'todo' setting ['$r'] for '$celement3'.";
						}
						next;
					}
					if($cattr eq "failcount") {
						if(!_valid_occurrence_minmax($x)) {
							push @e,"Failcount Min/Max setting of '$x' is not valid for '$celement3'.";
						}
						next;
					}
					if($cattr eq "occurrence_minmax") {
						if(!_valid_occurrence_minmax($x)) {
							push @e,"Occurance Min/Max setting of '$x' is not valid for '$celement3'.";
						}
						next;
					}
					if($cattr eq "threshold") {
						if(!_valid_threshold($x)) {
							push @e,"Threshold setting of '$x' is not valid for '$celement3'.";
						}
						next;
					}
					push @e,"Unexpected attribute '$cattr' found in action '$celement3'.";
				}
			}
		}

		if(@e) {
			return(0,@e);
		}
	}
	return(1);
}

sub copy {
my $self=shift;
my $copy;

	$copy={};
	$copy->{t}={%{$self->{t}}};
	$copy->{valid_structure}=$self->{valid_structure};
	$copy->{valid_content}=$self->{valid_content};
	$copy->{valid_actions}=[@{$self->{valid_actions}}];
	$copy->{type}=$self->{type};
	$copy->{value}=$self->{value};
	$copy->{valid_node_monitors}=[@{$self->{valid_node_monitors}}];
	$copy->{valid_app_monitors}=[@{$self->{valid_app_monitors}}];
	bless($copy);
	return $copy;
}

#########################################################################
# A couple of helper functions for the main monitor daemon now...	#
#########################################################################

sub interval_in_seconds {
my $self=shift;

	my $type=$self->{type}; # 0 - node type, 1 = app type
	my ($DATA,$attrs,$elements);
	if($type==0) {
		($DATA,$attrs,$elements)=get_element($self,"node_monitoring_configuration#0|monitor#0");
	} else {
		($DATA,$attrs,$elements)=get_element($self,"application_monitoring_configuration#0|monitor#0");
	} 
	my $interval=$attrs->{interval};
	if(!defined($interval)) {
		$interval=30; # Default to 30 seconds if not set
	}
	return _convert_interval($interval);
}

sub get_value {
my $self=shift;
	return $self->{value};
}

sub get_type {
my $self=shift;
	return $self->{type};
}

sub get_monitor_name_n_type {
my $self=shift;

	my $type=$self->{type};
	return(undef,undef) if !defined($type);
	my ($DATA,$attrs,$elements);
	if($type==0) {
		($DATA,$attrs,$elements)=get_element($self,"node_monitoring_configuration#0|monitor#0");
	} else {
		($DATA,$attrs,$elements)=get_element($self,"application_monitoring_configuration#0|monitor#0");
	}
	my $n=undef; my $t=undef;
	$n=$attrs->{name} if exists($attrs->{name});
	$t=$attrs->{type} if exists($attrs->{type});
	return ($n,$t)
}

#########################################################################
# return a number which is the match for current time - this cab be 	#
# sent to get_actions for a list of actions to compare against.		#
#########################################################################

sub matching_instance {
my $self=shift;
my @D=qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
my $top;

	my $type=$self->{type};
	if($type==1) {
		$top="application_monitoring_configuration#0|monitor#0";
	} else {
		$top="node_monitoring_configuration#0|monitor#0";
	}

	my $inst=0;
	my @t=localtime(time); my $day_today=lc(substr($D[$t[6]],0,3));
	my $hour_today=$t[2]; my $min_today=$t[1];
	my ($days,$from,$to);
	my $found=-1;
	while(1) {
		my ($DATA,$attrs,$elements)=get_element($self,"$top|instance#$inst");
		last if !defined($attrs);
		$days=$attrs->{days} if exists($attrs->{days});
		$from=$attrs->{from} if exists($attrs->{from});
		$to=$attrs->{to} if exists($attrs->{to});

		# A block for testing the days setting.

		my $skip=0;
TEST1:		{
		if(defined($days)) {
			if($days eq "all") {
				last TEST1;
			}
			my @D2=split(/,/,$days);
			for my $cday (@D2) {
				my $s=lc(substr($cday,0,3));
				if($s eq $day_today) {
					last TEST1;
				}
			}
			$skip=1;
		}
		}
		if($skip) { $inst++; next; }

		#########################################################
		# If we get here the days match, so check the		#
		# time.							#
		#########################################################

TEST2:		{
			last TEST2 if(!defined($from) && !defined($to));
			$from="00:00" if !defined($from);
			$to="24:00" if !defined($to);

			my @FR=map {int($_); } (split(/:/,$from));
			my @TT=map {int($_); } (split(/:/,$to));
			if($hour_today<$FR[0] || $hour_today>$TT[0]) {
				$skip=1; last TEST2;
			}
			if( ($hour_today==$FR[0] && $min_today<$FR[1]) ||
				($hour_today==$TT[0] && $min_today>$TT[1])) {
				$skip=1; last TEST2;
			}
		}
		if($skip) { $inst++; next; }

		#########################################################
		# If we get here then the current time / date		#
		# which the currnet instance, so return it.		#
		#########################################################
		$found=$inst;
		last;
	} 
	return $found; # Instance number or -1 if nothing suitable
}

# Get the rearm interval for the current instance that has been matched.

sub get_instance_rearm_period {
my $self=shift;
my $inst=shift;

	my $type=$self->{type};
	my $top;
	if($type==1) {
		$top="application_monitoring_configuration#0|monitor#0";
	} else {
		$top="node_monitoring_configuration#0|monitor#0";
	}
	my ($DATA,$attrs,$elements)=get_element($self,"$top|instance#$inst");
	return(undef) if !defined($DATA);
	if(exists($attrs->{rearm})) {
		return _convert_interval($attrs->{rearm});
	}
	return 300;
}

sub get_instance_quiesce_period {
my $self=shift;
my $inst=shift;

	my $type=$self->{type};
	my $top;
	if($type==1) {
		$top="application_monitoring_configuration#0|monitor#0";
	} else {
		$top="node_monitoring_configuration#0|monitor#0";
	}
	my ($DATA,$attrs,$elements)=get_element($self,"$top|instance#$inst");
	return(undef) if !defined($DATA);
	if(exists($attrs->{quiesce})) {
		return _convert_interval($attrs->{quiesce});
	}
	return 0;
}
sub get_action_details {
my $self=shift;
my $inst=shift;
my $action=shift;

	my $type=$self->{type};
	my $top;
	if($type==1) {
		$top="application_monitoring_configuration#0|monitor#0";
	} else {
		$top="node_monitoring_configuration#0|monitor#0";
	}
	my ($DATA,$attrs,$elements)=get_element($self,"$top|instance#$inst|action#$action");
	if(!defined($DATA)) {
		return undef;
	}
	return $attrs;
}

sub run_action {
my $self=shift;
my %args=@_;

	if(!exists($args{type}) || !exists($args{action})) {
		msg("Invalid parameters passed to 'run_action' - action ignored.");
		return;
	}

	if(! exists($self->{loaded}->{$args{action}})) {
		eval "undef &ACTION_$args{action}";
		do "$::TRUECL/monitors/actions/ACTION_$args{action}";
		$self->{loaded}->{$args{action}}=1;
	}
	if($args{type}==0) {
		eval "ACTION_$args{action}();";
	} else {
		eval "ACTION_$args{action}($args{application});";
	}
}

1;

